from collections import namedtuple
import numpy as np, itertools as it
from layout_parse import parse_expr
from char_rec2 import classify_cautiously,VARIANCE
from operator import __add__
from utils import *

CHAR_PENALTY = -5. #TODO: choose a reasonable value based on variance in char_rec2
StrokeParse = namedtuple("StrokeParse","starts score")
StrokeMatch = namedtuple("StrokeMatch","matches best_score")

STROKE_LIST = None



def last_same(seq1,seq2):
    last = None
    for el1,el2 in zip(seq1,seq2):
        if el1==el2: last = el1
        else: break
    return last
        
PREV_ENDS = ()
        
#@verbose
def parse_strokes(stroke_list):
    global STROKE_LIST,PREV_ENDS
    STROKE_LIST = stroke_list
    #starts = parse_subseq(0,len(stroke_list)).starts
    starts = stroke_starts()
    cprint("purple",starts)
    symbols = [Symbol(match_subseq(start,end).matches,
                       get_bounds(stroke_list[start:end]))
                   for (start,end) in zip(starts,starts[1:]+(len(stroke_list),))]
    ends = starts[1:]+(len(stroke_list),)
    revert_caches(last_same(ends,PREV_ENDS) or 0)
    cprint("yellow",ends,PREV_ENDS,(last_same(ends,PREV_ENDS) or 0))
    PREV_ENDS = ends
    
    # let's say only char is "i". Then starts = (0,). We want to revert caches to back when there were 
    # 0 characters
    result = parse_expr(symbols)
    if len(symbols) > 0: cprint("green",symbols[-1].matches)
    try: cprint("red",[symbol.matches[0].char for symbol in symbols])    
    except Exception: pass
    return result

@verbose
def stroke_starts():
    return tuple(find_all(lambda i: i == 0 or not strokes_cross_helper(i)  and not i_like(i),
                         xrange(0,len(STROKE_LIST))))

ILIKE_CUTOFF = -2.
#TWO_PART_CHARS = set(["i","=","j"])
TWO_PART_CHARS = set(["="])

@verbose
@memoized
def i_like(end):
    cprint("green",match_subseq(end-1,end+1).matches )
    matches = match_subseq(end-1,end+1).matches
    if matches == []: return False
    else:
        cprint("red",end,matches[0].char)
        return matches[0].char in TWO_PART_CHARS and matches[0].logp > ILIKE_CUTOFF

@memoized
def strokes_cross_helper(i):
    return strokes_cross(STROKE_LIST[i],STROKE_LIST[i-1])

def find_all(fn,vals):
    return [i for (i,val) in enumerate(vals) if fn(val)]
                                                     
def strokes_cross(stroke1,stroke2):
    return any(segments_cross(start1,end1,start2,end2)
               for (start1,end1) in zip(stroke1[:-1],stroke1[1:])
               for (start2,end2) in zip(stroke2[:-1],stroke2[1:])) 

def area_sign(u,v,w):    
    u_x,u_y = u
    v_x,v_y = v
    w_x,w_y = w
    return np.sign((u_x-w_x)*(v_y-w_y) - (u_y-w_y)*(v_x-w_x))

def segments_cross(start1,end1,start2,end2):
    return area_sign(start1,end1,start2) != area_sign(start1,end1,end2) and area_sign(start1,start2,end2) != area_sign(end1,start2,end2)

def get_bounds(stroke_list):
    all_pts = np.concatenate([np.array(stroke) for stroke in stroke_list])
    all_pts[:,1]*=-1
    xmin,ymin = all_pts.min(axis=0)
    xmax,ymax = all_pts.max(axis=0)
    return (xmin,xmax,ymin,ymax)
                                                    
#@verbose    
@memoized    
def parse_subseq(start,stop):
    "return list of segments starts and the score. start is the starting ind, stop is after the last ind"
    if start == stop: return StrokeParse((),0)
    stops = irange(start+1,min(stop,start+3))
    parses = [StrokeParse((start,)+parse_subseq(newstop,stop).starts,
                       CHAR_PENALTY+match_subseq(start,newstop).best_score + parse_subseq(newstop,stop).score)
              for newstop in stops]
    cprint("red",parses)
    return parses[np.argmax([parse.score for parse in parses])]


#@verbose
@memoized
def match_subseq(start,stop):
    strokes = [STROKE_LIST[ind] for ind in xrange(start,stop)]
    matches = classify_cautiously(strokes)
    return StrokeMatch(matches,safemax((match.logp for match in matches),default=-HUGE))
    